/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package x10.interfaces.cm15a;

import java.util.logging.Logger;

import x10.enums.InterfaceMessage;
import x10.enums.Interfaces;
import x10.event.X10Event;
import x10.eventServices.X10EventListener;
import x10.interfaces.AbstractDeviceInfo;
import x10.interfaces.cm15a.receive.CM15AReceiveFactory;
import x10.interfaces.cm15a.transmit.CM15ATransmitFactory;
import x10.utilities.ChunkOfBytes;
import x10.utilities.Utils;
import ch.ntb.usb.Device;
import ch.ntb.usb.USB;
import ch.ntb.usb.USBException;
import ch.ntb.usb.USBTimeoutException;

/**
 *
 * @author Denny
 */
public class CM15A extends AbstractDeviceInfo implements X10EventListener, Runnable {
    Logger logger = Logger.getLogger(this.getClass().getName());
    private static String eventListenerName = Interfaces.CM15A.toString();
    private CM15AReceiveFactory receiveFactory = new CM15AReceiveFactory();
    private CM15ATransmitFactory transmitFactory = new CM15ATransmitFactory();
    private static Device dev;
    boolean interfaceReady = false;
    private X10EventListener eventListener;
    
    public CM15A(X10EventListener eventListener) {
	super();
        this.eventListener = eventListener;
        connect();
    }

    /**
     * getEventListenerName() - returns the name of this X10EventListener class
     */
    public String getEventListenerName(){
	return eventListenerName;
    }
    
    @Override
    public String toString(){
	return eventListenerName;
    }
    
    public void receiveEvent(X10Event event){

	//Check to ensure the event didn't originate from this
	//interface ... if it didn't, enqueue it for transmission 
	if(!event.getSource().contentEquals(Interfaces.CM15A.toString())){
            transmitFactory.enqueue(event);
        }
    }

    public void transmitEvent(X10Event event){
	eventListener.receiveEvent(event);
    }

    public void connect(){
        try {
            initValues();
            openUsbDevice();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public void run(){
        boolean interfaceMessageFlag = false;
        InterfaceMessage interfaceMessage = InterfaceMessage.UNKNOWN;
        int interfaceReceiveCycles = 0;
        ChunkOfBytes receiveBytes1 = new ChunkOfBytes();
        ChunkOfBytes receiveBytes2 = new ChunkOfBytes();
        ChunkOfBytes transmitBytes;
        ChunkOfBytes interfaceBytes = new ChunkOfBytes();

        while(true){
            while(interfaceReady) {
                //RECEIVE

                //Interface response receive
                if(interfaceMessageFlag){
                    if(interfaceReceiveCycles == 1){
                        interfaceBytes = read();
                    } else if(interfaceReceiveCycles == 2){
                        receiveBytes1 = read();
                        receiveBytes2 = read();
                        interfaceBytes = Utils.mergeBytes(receiveBytes1, receiveBytes2);
                    } else {
                        System.out.println("Interface receive cycles error - not 1 or 2");
                    }

                    //Set interface flags and data, prior to dispatch
                    interfaceBytes.setInterfaceRequest(true);
                    interfaceBytes.setInterfaceMessage(interfaceMessage);
                    interfaceBytes.setInterfaceReceiveCycles(interfaceReceiveCycles);

                    //Cleanup
                    interfaceMessageFlag = false;
                    interfaceReceiveCycles = 0;

                    //Dispatch
                    receive(interfaceBytes);

                } else {

                    //Normal receive
                    receive(read());

                }

                //TRANSMIT
                if(transmitFactory.hasBytesToTransmit()){
                    transmitBytes = transmitFactory.getBytes();
                    write(transmitBytes);

                    //Check for interface message and set flag & type
                    if(transmitBytes.isInterfaceRequest()){
                        interfaceMessageFlag = true;
                        interfaceMessage = transmitBytes.getInterfaceMessage();
                        interfaceReceiveCycles = transmitBytes.getInterfaceReceiveCycles();
                    }
                }
            }
        }
    }

    private void receive(ChunkOfBytes bytes){
        if(!bytes.isEmpty()){
            X10Event receivedEvent = receiveFactory.dispatch(bytes);
            if(receivedEvent != null){
                transmitEvent(receivedEvent);
            }
        }
    }

    public boolean isInterfaceReady() {
        return interfaceReady;
    }

    public void setInterfaceReady(boolean interfaceReady) {
        this.interfaceReady = interfaceReady;
    }

    @Override
    public  void initValues() {
        setIdVendor((short) 0xbc7);
        setIdProduct((short) 0x1);
        setTimeout(500);  //originally set at 500
        setConfiguration(1);
        setInterface(0);
        setAltinterface(-1);
        setOutEPBulk(0x02);
        setInEPBulk(0x81);
        setOutEPInt(0x02);
        setInEPInt(0x81);
        setSleepTimeout(500);
        setMaxDataSize(USB.FULLSPEED_MAX_BULK_PACKET_SIZE);
        setMode(TransferMode.Bulk);
    }

    public void openUsbDevice() {
        dev = USB.getDevice(getIdVendor(), getIdProduct());
        try {
            dev.open(getConfiguration(), getInterface(), getAltinterface());
            interfaceReady = true;
        } catch (USBException e) {
                e.printStackTrace();
        }
    }

    public void write(ChunkOfBytes bytes) {
        byte[] data = bytes.getBytes();
        int length = bytes.getLength();

        if(!bytes.isEmpty()){
            try {
                dev.writeBulk(getOutEPInt(), data, length,  2000, false);
            } catch (USBException e) {
                e.printStackTrace();
            }
        }

    }

    public ChunkOfBytes read() {
        ChunkOfBytes bytes = new ChunkOfBytes();
        byte[] readBytes = new byte[dev.getMaxPacketSize()];
        int readBytesLength = 0;
        try {
            readBytesLength = dev.readBulk(getInEPInt(), readBytes, dev.getMaxPacketSize(), getTimeout(), false);
            //Above throws USBTimeoutException if no data to read, and progresses no farther

            bytes.setBytes(readBytes);
            bytes.setLength(readBytesLength);
            bytes.setEmpty(false);
        } catch (USBTimeoutException toe){
            //Do Nothing ... thrown when there is no data to read.  hmmm, should we use interrupt read?
        } catch (USBException e) {
            e.printStackTrace();
        }
        return bytes;
    }

}
